import asyncio
import json

from bilibili_api import search, video, Credential, comment
from bilibili_api.search import SearchObjectType
from bilibili_api.comment import CommentResourceType
from mcp.server.fastmcp import FastMCP
from playwright.async_api import async_playwright, expect
from urllib.parse import urlparse

app = FastMCP(
    "bilibili-mcp-server",
    instructions="bilibili(也称呼为B站)的MCP Server，能自动实现登录B站、获取视频信息、活动、音频等功能"
)

@app.tool()
async def search_video(keyword: str, page: int = 1, page_size: int = 20):
    """
    在B站上搜索指定关键字的视频
    :param keyword: 用户输入的关键字
    :param page: 页码
    :param page_size: 用户要求一页限制搜索多少个
    :return: 在B站搜索后的结果
    """
    result = await search.search_by_type(keyword=keyword, page=page, page_size=page_size,search_type=SearchObjectType.VIDEO)
    # with open("video.json", "w", encoding="utf-8") as f:
    #     json.dump(result, f, ensure_ascii=False)  # dump表示转换成json, ensure_ascii=False不要允许Ascii编码不然中文就被转换了
    return_result = {
        "page": page,
        "page_size": result["pagesize"],
        "num_pages": result["numPages"],
        "videos": [
            {
                "id": video["id"],
                "author": video["author"], # 作者名称
                "author_pic": video["upic"], # 作者头像
                "author_id": video["mid"], # UP 主（作者）的唯一标识 ID（mid = UP 主的用户 ID）
                "typename": video["typename"], # 视频二级分区名称
                "video_url": video["arcurl"], # 视频访问 URL（旧版 av 格式链接
                "video_pic": "https:" + video["pic"], # 视频封面图片 URL
                "bvid": video["bvid"], # Bilibili新版唯一视频id
                "title": video["title"], # 视频标题
                "description": video["description"], # 视频简介
                "tag": video["tag"], # 视频标签
                "play": video["play"], # 播放量
                "like": video["like"], # 点赞量
                "review": video["review"], # 评论数
                "favorites": video["favorites"], # 收藏量
                "pubdate": video["pubdate"], # 视频发布时间（Unix 时间戳，单位：秒）
                "duration": video["duration"], # 视频时长（格式 分:秒，如 10:53 表示 10 分 53 秒）
                "danmaku": video["danmaku"], # 视频弹幕书
            }for video in result["result"]
        ]
    }
    return return_result


@app.tool()
async def login_bilibili_mobile_and_password(mobile: str, password: str):
    """
    这是基于账号和密码登录的方式：
    通过输入手机号和登陆密码登录B站，登录过程需要用户自己处理图形验证码和短信验证码，登陆成功则返回cookie信息，登录失败则返回None
    :param mobile: 用户登录的手机号
    :param password: 用户登录密码
    :return: 登陆成功则返回cookie信息，登录失败则返回None
    """
    async with async_playwright() as p:
        context, page = await open_bilibili(p)

        # 1.先确保网页加载完成
        await page.wait_for_load_state(state="domcontentloaded")
        # 2.寻找登录icon并点击
        await page.locator(selector=".header-login-entry").click()
        # 3.查找输入/密码框并输入
        await page.get_by_placeholder(text="请输入账号").fill(mobile)
        await page.get_by_placeholder(text="请输入密码").fill(password)
        # 4.找到登录按钮并点击
        await page.locator(".btn_primary").click()
        # 5.等待验证码出现
        await page.wait_for_selector(".geetest_widget",state="visible")
        # 2Captcha 验证码识别

        # 等待用户通过验证码
        await page.wait_for_selector(".geetest_widget", state="hidden")
        print("验证码操作完成")

        # 图形验证码操作完成后，如果账号有异常，可能还需要跳转到短信验证码的页面
        # try:
        #     login_btn = await page.wait_for_selector(".risk-input-after-active",state="visible",timeout=5000)
        #     await login_btn.click()
        #     await page.wait_for_selector(".risk-input-after-active", state="hidden")
        #     print("短线验证码操作完成")
        #     await page.wait_for_timeout(3000)
        # except Exception:
        #     print("短线验证码未出现")

        return_cookies = await handler_cookie(context, page)

        return return_cookies
        # 由于异步上下文执行完成会自动关闭浏览器，不方便开发，需要等待
        # await page.wait_for_timeout(100000)


async def handler_cookie(context, page) -> dict:
    """
    判断是否跳转到首页并基于上下文获取cookie
    :param context: 浏览器上下文
    :param page: 当前浏览器页面
    :return: 返回获取到的cookie
    """
    # 等待页面跳转到首页
    await page.wait_for_timeout(3000)
    # 在判断是否到首页了
    await expect(page).to_have_title("哔哩哔哩 (゜-゜)つロ 干杯~-bilibili", timeout=60000)

    # 如果代码走到这说明登录成功了，获取cookie
    cookies = await context.cookies()
    return_cookies = {}
    for cookie in cookies:
        if cookie["name"] in ["bili_jct", "SESSDATA"]:
            return_cookies[cookie["name"]] = cookie["value"]
    return return_cookies


@app.tool()
async def login_bilibili_mobile(mobile: str):
    """
    这是基于短信验证码登录的方式：
    仅输入手机号即可登录B站，登录过程需要用户自己处理图形验证码和短信验证码，登陆成功则返回cookie信息，登录失败则返回None
    :param mobile: 用户登录的手机号
    :return: 登陆成功则返回cookie信息，登录失败则返回None
    """
    async with async_playwright() as p:
        context, page = await open_bilibili(p)

        # 1.先确保网页加载完成
        await page.wait_for_load_state(state="domcontentloaded")
        # 2.寻找登录icon并点击
        await page.locator(selector=".header-login-entry").click()
        await page.wait_for_timeout(1500)
        # 3.查找短信登录Tab并点击
        await page.locator(selector=".login-tab-item",has_text="短信登录").click()
        # 4.查找输入/密码框并输入
        await page.get_by_placeholder(text="请输入手机号").fill(mobile)
        # 这是输入完成手机号有时候不定期弹出二级校验弹窗
        try:
            cancel_btn = await page.wait_for_selector(
                ".dialog__footer > div:first-child",  # 精准定位第一个 div 子元素
                state="visible",
                timeout=3000
            )
            await cancel_btn.click(wait_for="hidden") # 点击后等待元素隐藏（适合关闭弹窗场景）
            print("点击的元素文本：", await cancel_btn.text_content())
            # await page.wait_for_selector(".dialog__footer", state="hidden")
            print("二次校验操作完成")
        except Exception:
            print("二次校验未出现")
        # 5.找到发送验证码按钮并点击
        await page.locator(".login-sms-send").click()

        # 等待用户通过短信验证码，最多等待1分钟
        await page.wait_for_selector(".bili-mini-content-wp", state="hidden",timeout=60000)
        print("短信验证码操作完成")

        # 等待页面跳转到首页
        return_cookies = await handler_cookie(context, page)
        return return_cookies


async def open_bilibili(p):
    """
    基于 playwright 打开bilibili网页
    :param p: playwright异步操作对象
    :return: 返回上下文和bilibili首页对象
    """
    browser = await p.chromium.launch(headless=False)  # headless表示无头，设置成False是为了可以弹出浏览器
    context = await browser.new_context()
    page = await context.new_page()
    await page.goto("https://bilibili.com")
    # print(await page.title()) # 获取浏览器页面标题
    # await browser.close() # 方法是异步会自动执行关闭
    return context, page


@app.tool()
async def like_video(url_or_bvid: str, SESSDATA: str, bili_jct: str):
    """
    给bilibili指定的视频点赞，此操作必须登录之后才能执行，SESSDATA和bili_jct是登录bilibili之后返回的cookie信息当中
    :param url_or_bvid: 视频的url或者对应的bvid
    :param SESSDATA: SESSDATA信息，登录成功之后返回的cookie信息中获取
    :param bili_jct: bili_jct信息，登录成功之后返回的cookie信息中获取
    :return: 点赞成功返回True,否则返回错误信息
    """
    try:
        bvid = await _exact_bvid(url_or_bvid)
        credential = Credential(sessdata=SESSDATA, bili_jct=bili_jct)
        v = video.Video(bvid=bvid, credential=credential)
        await v.like()
        return True
    except Exception as e:
        return str(e)


def _exact_bvid(url_or_bvid):
    """提取bvid"""
    if url_or_bvid.startswith("http"):
        bvid = urlparse(url_or_bvid).path.strip("/").split("/")[-1]
    else:
        bvid = url_or_bvid
    return bvid


@app.tool()
async def send_comment(url_or_bvid: str,text: str,SESSDATA: str, bili_jct: str):
    """
    给指定视频发送评论
    :param url_or_bvid: 视频的url或者对应的bvid
    :param text: 评论内容
    :param SESSDATA: SESSDATA信息，登录成功之后返回的cookie信息中获取
    :param bili_jct: bili_jct，登录成功之后返回的cookie信息中获取
    :return: 发送成功返回True，否则返回失败信息
    """
    try:
        bvid = _exact_bvid(url_or_bvid)
        v = video.Video(bvid=bvid)
        video_info = await v.get_info()
        credential = Credential(sessdata=SESSDATA, bili_jct=bili_jct)
        result = await comment.send_comment(text=text, oid=video_info["aid"], type_=CommentResourceType.VIDEO, credential=credential)
        return f"发送评论成功,result为{result}"
    except Exception as e:
        return "发送失败，原因：" + str(e)


async def main():
    # result = await search_video("钢铁侠")
    # result = await login_bilibili(mobile="13245877520",password="7758258gG?")
    result = await login_bilibili_mobile(mobile="13245877520")
    final_result = await send_comment(
        url_or_bvid="https://www.bilibili.com/video/BV1Ta4y1J7QL/?spm_id_from=333.337.search-card.all.click&vd_source=219db09faf905d69680d87a463fe89c3",
        text="哇塞视频很好，很喜欢！",
        SESSDATA=result["SESSDATA"],
        bili_jct=result["bili_jct"]
    )
    print(final_result)


if __name__ == "__main__":
    app.run(transport='stdio')
    # asyncio.run(main())
